/*
  html2canvas 0.4.1 <http://html2canvas.hertzen.com>
  Copyright (c) 2013 Niklas von Hertzen

  Released under MIT License
*/

(function(window, document, undefined) {

	"use strict";

	var _html2canvas = {},
		previousElement,
		computedCSS,
		html2canvas;

	_html2canvas.Util = {};

	_html2canvas.Util.log = function(a) {
		if(_html2canvas.logging && window.console && window.console.log) {
			window.console.log(a);
		}
	};

	_html2canvas.Util.trimText = (function(isNative) {
		return function(input) {
			return isNative ? isNative.apply(input) : ((input || '') + '').replace(/^\s+|\s+$/g, '');
		};
	})(String.prototype.trim);

	_html2canvas.Util.asFloat = function(v) {
		return parseFloat(v);
	};

	(function() {
		// TODO: support all possible length values
		var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
		var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
		_html2canvas.Util.parseTextShadows = function(value) {
			if(!value || value === 'none') {
				return [];
			}

			// find multiple shadow declarations
			var shadows = value.match(TEXT_SHADOW_PROPERTY),
				results = [];
			for(var i = 0; shadows && (i < shadows.length); i++) {
				var s = shadows[i].match(TEXT_SHADOW_VALUES);
				results.push({
					color: s[0],
					offsetX: s[1] ? s[1].replace('px', '') : 0,
					offsetY: s[2] ? s[2].replace('px', '') : 0,
					blur: s[3] ? s[3].replace('px', '') : 0
				});
			}
			return results;
		};
	})();

	_html2canvas.Util.parseBackgroundImage = function(value) {
		var whitespace = ' \r\n\t',
			method, definition, prefix, prefix_i, block, results = [],
			c, mode = 0,
			numParen = 0,
			quote, args;

		var appendResult = function() {
			if(method) {
				if(definition.substr(0, 1) === '"') {
					definition = definition.substr(1, definition.length - 2);
				}
				if(definition) {
					args.push(definition);
				}
				if(method.substr(0, 1) === '-' &&
					(prefix_i = method.indexOf('-', 1) + 1) > 0) {
					prefix = method.substr(0, prefix_i);
					method = method.substr(prefix_i);
				}
				results.push({
					prefix: prefix,
					method: method.toLowerCase(),
					value: block,
					args: args
				});
			}
			args = []; //for some odd reason, setting .length = 0 didn't work in safari
			method =
				prefix =
				definition =
				block = '';
		};

		appendResult();
		for(var i = 0, ii = value.length; i < ii; i++) {
			c = value[i];
			if(mode === 0 && whitespace.indexOf(c) > -1) {
				continue;
			}
			switch(c) {
				case '"':
					if(!quote) {
						quote = c;
					} else if(quote === c) {
						quote = null;
					}
					break;

				case '(':
					if(quote) {
						break;
					} else if(mode === 0) {
						mode = 1;
						block += c;
						continue;
					} else {
						numParen++;
					}
					break;

				case ')':
					if(quote) {
						break;
					} else if(mode === 1) {
						if(numParen === 0) {
							mode = 0;
							block += c;
							appendResult();
							continue;
						} else {
							numParen--;
						}
					}
					break;

				case ',':
					if(quote) {
						break;
					} else if(mode === 0) {
						appendResult();
						continue;
					} else if(mode === 1) {
						if(numParen === 0 && !method.match(/^url$/i)) {
							args.push(definition);
							definition = '';
							block += c;
							continue;
						}
					}
					break;
			}

			block += c;
			if(mode === 0) {
				method += c;
			} else {
				definition += c;
			}
		}
		appendResult();

		return results;
	};

	_html2canvas.Util.Bounds = function(element) {
		var clientRect, bounds = {};

		if(element.getBoundingClientRect) {
			clientRect = element.getBoundingClientRect();

			// TODO add scroll position to bounds, so no scrolling of window necessary
			bounds.top = clientRect.top;
			bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
			bounds.left = clientRect.left;

			bounds.width = element.offsetWidth;
			bounds.height = element.offsetHeight;
		}

		return bounds;
	};

	// TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
	// but would require further work to calculate the correct positions for elements with offsetParents
	_html2canvas.Util.OffsetBounds = function(element) {
		var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {
			top: 0,
			left: 0
		};

		return {
			top: element.offsetTop + parent.top,
			bottom: element.offsetTop + element.offsetHeight + parent.top,
			left: element.offsetLeft + parent.left,
			width: element.offsetWidth,
			height: element.offsetHeight
		};
	};

	function toPX(element, attribute, value) {
		var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
			left,
			style = element.style;

		// Check if we are not dealing with pixels, (Opera has issues with this)
		// Ported from jQuery css.js
		// From the awesome hack by Dean Edwards
		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

		// If we're not dealing with a regular pixel number
		// but a number that has a weird ending, we need to convert it to pixels

		if(!/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test(value) && /^-?\d/.test(value)) {
			// Remember the original values
			left = style.left;

			// Put in the new values to get a computed value out
			if(rsLeft) {
				element.runtimeStyle.left = element.currentStyle.left;
			}
			style.left = attribute === "fontSize" ? "1em" : (value || 0);
			value = style.pixelLeft + "px";

			// Revert the changed values
			style.left = left;
			if(rsLeft) {
				element.runtimeStyle.left = rsLeft;
			}
		}

		if(!/^(thin|medium|thick)$/i.test(value)) {
			return Math.round(parseFloat(value)) + "px";
		}

		return value;
	}

	function asInt(val) {
		return parseInt(val, 10);
	}

	function parseBackgroundSizePosition(value, element, attribute, index) {
		value = (value || '').split(',');
		value = value[index || 0] || value[0] || 'auto';
		value = _html2canvas.Util.trimText(value).split(' ');

		if(attribute === 'backgroundSize' && (!value[0] || value[0].match(/cover|contain|auto/))) {
			//these values will be handled in the parent function
		} else {
			value[0] = (value[0].indexOf("%") === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
			if(value[1] === undefined) {
				if(attribute === 'backgroundSize') {
					value[1] = 'auto';
					return value;
				} else {
					// IE 9 doesn't return double digit always
					value[1] = value[0];
				}
			}
			value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
		}
		return value;
	}

	_html2canvas.Util.getCSS = function(element, attribute, index) {
		if(previousElement !== element) {
			computedCSS = document.defaultView.getComputedStyle(element, null);
		}

		var value = computedCSS[attribute];

		if(/^background(Size|Position)$/.test(attribute)) {
			return parseBackgroundSizePosition(value, element, attribute, index);
		} else if(/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) {
			var arr = value.split(" ");
			if(arr.length <= 1) {
				arr[1] = arr[0];
			}
			return arr.map(asInt);
		}

		return value;
	};

	_html2canvas.Util.resizeBounds = function(current_width, current_height, target_width, target_height, stretch_mode) {
		var target_ratio = target_width / target_height,
			current_ratio = current_width / current_height,
			output_width, output_height;

		if(!stretch_mode || stretch_mode === 'auto') {
			output_width = target_width;
			output_height = target_height;
		} else if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
			output_height = target_height;
			output_width = target_height * current_ratio;
		} else {
			output_width = target_width;
			output_height = target_width / current_ratio;
		}

		return {
			width: output_width,
			height: output_height
		};
	};

	function backgroundBoundsFactory(prop, el, bounds, image, imageIndex, backgroundSize) {
		var bgposition = _html2canvas.Util.getCSS(el, prop, imageIndex),
			topPos,
			left,
			percentage,
			val;

		if(bgposition.length === 1) {
			val = bgposition[0];

			bgposition = [];

			bgposition[0] = val;
			bgposition[1] = val;
		}

		if(bgposition[0].toString().indexOf("%") !== -1) {
			percentage = (parseFloat(bgposition[0]) / 100);
			left = bounds.width * percentage;
			if(prop !== 'backgroundSize') {
				left -= (backgroundSize || image).width * percentage;
			}
		} else {
			if(prop === 'backgroundSize') {
				if(bgposition[0] === 'auto') {
					left = image.width;
				} else {
					if(/contain|cover/.test(bgposition[0])) {
						var resized = _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, bgposition[0]);
						left = resized.width;
						topPos = resized.height;
					} else {
						left = parseInt(bgposition[0], 10);
					}
				}
			} else {
				left = parseInt(bgposition[0], 10);
			}
		}

		if(bgposition[1] === 'auto') {
			topPos = left / image.width * image.height;
		} else if(bgposition[1].toString().indexOf("%") !== -1) {
			percentage = (parseFloat(bgposition[1]) / 100);
			topPos = bounds.height * percentage;
			if(prop !== 'backgroundSize') {
				topPos -= (backgroundSize || image).height * percentage;
			}

		} else {
			topPos = parseInt(bgposition[1], 10);
		}

		return [left, topPos];
	}

	_html2canvas.Util.BackgroundPosition = function(el, bounds, image, imageIndex, backgroundSize) {
		var result = backgroundBoundsFactory('backgroundPosition', el, bounds, image, imageIndex, backgroundSize);
		return {
			left: result[0],
			top: result[1]
		};
	};

	_html2canvas.Util.BackgroundSize = function(el, bounds, image, imageIndex) {
		var result = backgroundBoundsFactory('backgroundSize', el, bounds, image, imageIndex);
		return {
			width: result[0],
			height: result[1]
		};
	};

	_html2canvas.Util.Extend = function(options, defaults) {
		for(var key in options) {
			if(options.hasOwnProperty(key)) {
				defaults[key] = options[key];
			}
		}
		return defaults;
	};

	/*
	 * Derived from jQuery.contents()
	 * Copyright 2010, John Resig
	 * Dual licensed under the MIT or GPL Version 2 licenses.
	 * http://jquery.org/license
	 */
	_html2canvas.Util.Children = function(elem) {
		var children;
		try {
			children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(array) {
				var ret = [];
				if(array !== null) {
					(function(first, second) {
						var i = first.length,
							j = 0;

						if(typeof second.length === "number") {
							for(var l = second.length; j < l; j++) {
								first[i++] = second[j];
							}
						} else {
							while(second[j] !== undefined) {
								first[i++] = second[j++];
							}
						}

						first.length = i;

						return first;
					})(ret, array);
				}
				return ret;
			})(elem.childNodes);

		} catch(ex) {
			_html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
			children = [];
		}
		return children;
	};

	_html2canvas.Util.isTransparent = function(backgroundColor) {
		return(backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
	};
	_html2canvas.Util.Font = (function() {

		var fontData = {};

		return function(font, fontSize, doc) {
			if(fontData[font + "-" + fontSize] !== undefined) {
				return fontData[font + "-" + fontSize];
			}

			var container = doc.createElement('div'),
				img = doc.createElement('img'),
				span = doc.createElement('span'),
				sampleText = 'Hidden Text',
				baseline,
				middle,
				metricsObj;

			container.style.visibility = "hidden";
			container.style.fontFamily = font;
			container.style.fontSize = fontSize;
			container.style.margin = 0;
			container.style.padding = 0;

			doc.body.appendChild(container);

			// http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif)
			img.src = "";
			img.width = 1;
			img.height = 1;

			img.style.margin = 0;
			img.style.padding = 0;
			img.style.verticalAlign = "baseline";

			span.style.fontFamily = font;
			span.style.fontSize = fontSize;
			span.style.margin = 0;
			span.style.padding = 0;

			span.appendChild(doc.createTextNode(sampleText));
			container.appendChild(span);
			container.appendChild(img);
			baseline = (img.offsetTop - span.offsetTop) + 1;

			container.removeChild(span);
			container.appendChild(doc.createTextNode(sampleText));

			container.style.lineHeight = "normal";
			img.style.verticalAlign = "super";

			middle = (img.offsetTop - container.offsetTop) + 1;
			metricsObj = {
				baseline: baseline,
				lineWidth: 1,
				middle: middle
			};

			fontData[font + "-" + fontSize] = metricsObj;

			doc.body.removeChild(container);

			return metricsObj;
		};
	})();

	(function() {
		var Util = _html2canvas.Util,
			Generate = {};

		_html2canvas.Generate = Generate;

		var reGradients = [
			/^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
			/^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
			/^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/,
			/^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/,
			/^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/,
			/^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/,
			/^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/
		];

		/*
		 * TODO: Add IE10 vendor prefix (-ms) support
		 * TODO: Add W3C gradient (linear-gradient) support
		 * TODO: Add old Webkit -webkit-gradient(radial, ...) support
		 * TODO: Maybe some RegExp optimizations are possible ;o)
		 */
		Generate.parseGradient = function(css, bounds) {
			var gradient, i, len = reGradients.length,
				m1, stop, m2, m2Len, step, m3, tl, tr, br, bl;

			for(i = 0; i < len; i += 1) {
				m1 = css.match(reGradients[i]);
				if(m1) {
					break;
				}
			}

			if(m1) {
				switch(m1[1]) {
					case '-webkit-linear-gradient':
					case '-o-linear-gradient':

						gradient = {
							type: 'linear',
							x0: null,
							y0: null,
							x1: null,
							y1: null,
							colorStops: []
						};

						// get coordinates
						m2 = m1[2].match(/\w+/g);
						if(m2) {
							m2Len = m2.length;
							for(i = 0; i < m2Len; i += 1) {
								switch(m2[i]) {
									case 'top':
										gradient.y0 = 0;
										gradient.y1 = bounds.height;
										break;

									case 'right':
										gradient.x0 = bounds.width;
										gradient.x1 = 0;
										break;

									case 'bottom':
										gradient.y0 = bounds.height;
										gradient.y1 = 0;
										break;

									case 'left':
										gradient.x0 = 0;
										gradient.x1 = bounds.width;
										break;
								}
							}
						}
						if(gradient.x0 === null && gradient.x1 === null) { // center
							gradient.x0 = gradient.x1 = bounds.width / 2;
						}
						if(gradient.y0 === null && gradient.y1 === null) { // center
							gradient.y0 = gradient.y1 = bounds.height / 2;
						}

						// get colors and stops
						m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
						if(m2) {
							m2Len = m2.length;
							step = 1 / Math.max(m2Len - 1, 1);
							for(i = 0; i < m2Len; i += 1) {
								m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
								if(m3[2]) {
									stop = parseFloat(m3[2]);
									if(m3[3] === '%') {
										stop /= 100;
									} else { // px - stupid opera
										stop /= bounds.width;
									}
								} else {
									stop = i * step;
								}
								gradient.colorStops.push({
									color: m3[1],
									stop: stop
								});
							}
						}
						break;

					case '-webkit-gradient':

						gradient = {
							type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions
							x0: 0,
							y0: 0,
							x1: 0,
							y1: 0,
							colorStops: []
						};

						// get coordinates
						m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/);
						if(m2) {
							gradient.x0 = (m2[1] * bounds.width) / 100;
							gradient.y0 = (m2[2] * bounds.height) / 100;
							gradient.x1 = (m2[3] * bounds.width) / 100;
							gradient.y1 = (m2[4] * bounds.height) / 100;
						}

						// get colors and stops
						m2 = m1[4].match(/((?:from|to|color-stop)\((?:[0-9\.]+,\s)?(?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)\))+/g);
						if(m2) {
							m2Len = m2.length;
							for(i = 0; i < m2Len; i += 1) {
								m3 = m2[i].match(/(from|to|color-stop)\(([0-9\.]+)?(?:,\s)?((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\)/);
								stop = parseFloat(m3[2]);
								if(m3[1] === 'from') {
									stop = 0.0;
								}
								if(m3[1] === 'to') {
									stop = 1.0;
								}
								gradient.colorStops.push({
									color: m3[3],
									stop: stop
								});
							}
						}
						break;

					case '-moz-linear-gradient':

						gradient = {
							type: 'linear',
							x0: 0,
							y0: 0,
							x1: 0,
							y1: 0,
							colorStops: []
						};

						// get coordinates
						m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);

						// m2[1] == 0%   -> left
						// m2[1] == 50%  -> center
						// m2[1] == 100% -> right

						// m2[2] == 0%   -> top
						// m2[2] == 50%  -> center
						// m2[2] == 100% -> bottom

						if(m2) {
							gradient.x0 = (m2[1] * bounds.width) / 100;
							gradient.y0 = (m2[2] * bounds.height) / 100;
							gradient.x1 = bounds.width - gradient.x0;
							gradient.y1 = bounds.height - gradient.y0;
						}

						// get colors and stops
						m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}%)?)+/g);
						if(m2) {
							m2Len = m2.length;
							step = 1 / Math.max(m2Len - 1, 1);
							for(i = 0; i < m2Len; i += 1) {
								m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/);
								if(m3[2]) {
									stop = parseFloat(m3[2]);
									if(m3[3]) { // percentage
										stop /= 100;
									}
								} else {
									stop = i * step;
								}
								gradient.colorStops.push({
									color: m3[1],
									stop: stop
								});
							}
						}
						break;

					case '-webkit-radial-gradient':
					case '-moz-radial-gradient':
					case '-o-radial-gradient':

						gradient = {
							type: 'circle',
							x0: 0,
							y0: 0,
							x1: bounds.width,
							y1: bounds.height,
							cx: 0,
							cy: 0,
							rx: 0,
							ry: 0,
							colorStops: []
						};

						// center
						m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
						if(m2) {
							gradient.cx = (m2[1] * bounds.width) / 100;
							gradient.cy = (m2[2] * bounds.height) / 100;
						}

						// size
						m2 = m1[3].match(/\w+/);
						m3 = m1[4].match(/[a-z\-]*/);
						if(m2 && m3) {
							switch(m3[0]) {
								case 'farthest-corner':
								case 'cover': // is equivalent to farthest-corner
								case '': // mozilla removes "cover" from definition :(
									tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
									tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
									br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
									bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
									gradient.rx = gradient.ry = Math.max(tl, tr, br, bl);
									break;
								case 'closest-corner':
									tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
									tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
									br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
									bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
									gradient.rx = gradient.ry = Math.min(tl, tr, br, bl);
									break;
								case 'farthest-side':
									if(m2[0] === 'circle') {
										gradient.rx = gradient.ry = Math.max(
											gradient.cx,
											gradient.cy,
											gradient.x1 - gradient.cx,
											gradient.y1 - gradient.cy
										);
									} else { // ellipse

										gradient.type = m2[0];

										gradient.rx = Math.max(
											gradient.cx,
											gradient.x1 - gradient.cx
										);
										gradient.ry = Math.max(
											gradient.cy,
											gradient.y1 - gradient.cy
										);
									}
									break;
								case 'closest-side':
								case 'contain': // is equivalent to closest-side
									if(m2[0] === 'circle') {
										gradient.rx = gradient.ry = Math.min(
											gradient.cx,
											gradient.cy,
											gradient.x1 - gradient.cx,
											gradient.y1 - gradient.cy
										);
									} else { // ellipse

										gradient.type = m2[0];

										gradient.rx = Math.min(
											gradient.cx,
											gradient.x1 - gradient.cx
										);
										gradient.ry = Math.min(
											gradient.cy,
											gradient.y1 - gradient.cy
										);
									}
									break;

									// TODO: add support for "30px 40px" sizes (webkit only)
							}
						}

						// color stops
						m2 = m1[5].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
						if(m2) {
							m2Len = m2.length;
							step = 1 / Math.max(m2Len - 1, 1);
							for(i = 0; i < m2Len; i += 1) {
								m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
								if(m3[2]) {
									stop = parseFloat(m3[2]);
									if(m3[3] === '%') {
										stop /= 100;
									} else { // px - stupid opera
										stop /= bounds.width;
									}
								} else {
									stop = i * step;
								}
								gradient.colorStops.push({
									color: m3[1],
									stop: stop
								});
							}
						}
						break;
				}
			}

			return gradient;
		};

		function addScrollStops(grad) {
			return function(colorStop) {
				try {
					grad.addColorStop(colorStop.stop, colorStop.color);
				} catch(e) {
					Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]);
				}
			};
		}

		Generate.Gradient = function(src, bounds) {
			if(bounds.width === 0 || bounds.height === 0) {
				return;
			}

			var canvas = document.createElement('canvas'),
				ctx = canvas.getContext('2d'),
				gradient, grad;

			canvas.width = bounds.width;
			canvas.height = bounds.height;

			// TODO: add support for multi defined background gradients
			gradient = _html2canvas.Generate.parseGradient(src, bounds);

			if(gradient) {
				switch(gradient.type) {
					case 'linear':
						grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
						gradient.colorStops.forEach(addScrollStops(grad));
						ctx.fillStyle = grad;
						ctx.fillRect(0, 0, bounds.width, bounds.height);
						break;

					case 'circle':
						grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
						gradient.colorStops.forEach(addScrollStops(grad));
						ctx.fillStyle = grad;
						ctx.fillRect(0, 0, bounds.width, bounds.height);
						break;

					case 'ellipse':
						var canvasRadial = document.createElement('canvas'),
							ctxRadial = canvasRadial.getContext('2d'),
							ri = Math.max(gradient.rx, gradient.ry),
							di = ri * 2;

						canvasRadial.width = canvasRadial.height = di;

						grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
						gradient.colorStops.forEach(addScrollStops(grad));

						ctxRadial.fillStyle = grad;
						ctxRadial.fillRect(0, 0, di, di);

						ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color;
						ctx.fillRect(0, 0, canvas.width, canvas.height);
						ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
						break;
				}
			}

			return canvas;
		};

		Generate.ListAlpha = function(number) {
			var tmp = "",
				modulus;

			do {
				modulus = number % 26;
				tmp = String.fromCharCode((modulus) + 64) + tmp;
				number = number / 26;
			} while ((number * 26) > 26);

			return tmp;
		};

		Generate.ListRoman = function(number) {
			var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"],
				decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
				roman = "",
				v,
				len = romanArray.length;

			if(number <= 0 || number >= 4000) {
				return number;
			}

			for(v = 0; v < len; v += 1) {
				while(number >= decimal[v]) {
					number -= decimal[v];
					roman += romanArray[v];
				}
			}

			return roman;
		};
	})();

	function h2cRenderContext(width, height) {
		var storage = [];
		return {
			storage: storage,
			width: width,
			height: height,
			clip: function() {
				storage.push({
					type: "function",
					name: "clip",
					'arguments': arguments
				});
			},
			translate: function() {
				storage.push({
					type: "function",
					name: "translate",
					'arguments': arguments
				});
			},
			fill: function() {
				storage.push({
					type: "function",
					name: "fill",
					'arguments': arguments
				});
			},
			save: function() {
				storage.push({
					type: "function",
					name: "save",
					'arguments': arguments
				});
			},
			restore: function() {
				storage.push({
					type: "function",
					name: "restore",
					'arguments': arguments
				});
			},
			fillRect: function() {
				storage.push({
					type: "function",
					name: "fillRect",
					'arguments': arguments
				});
			},
			createPattern: function() {
				storage.push({
					type: "function",
					name: "createPattern",
					'arguments': arguments
				});
			},
			drawShape: function() {

				var shape = [];

				storage.push({
					type: "function",
					name: "drawShape",
					'arguments': shape
				});

				return {
					moveTo: function() {
						shape.push({
							name: "moveTo",
							'arguments': arguments
						});
					},
					lineTo: function() {
						shape.push({
							name: "lineTo",
							'arguments': arguments
						});
					},
					arcTo: function() {
						shape.push({
							name: "arcTo",
							'arguments': arguments
						});
					},
					bezierCurveTo: function() {
						shape.push({
							name: "bezierCurveTo",
							'arguments': arguments
						});
					},
					quadraticCurveTo: function() {
						shape.push({
							name: "quadraticCurveTo",
							'arguments': arguments
						});
					}
				};

			},
			drawImage: function() {
				storage.push({
					type: "function",
					name: "drawImage",
					'arguments': arguments
				});
			},
			fillText: function() {
				storage.push({
					type: "function",
					name: "fillText",
					'arguments': arguments
				});
			},
			setVariable: function(variable, value) {
				storage.push({
					type: "variable",
					name: variable,
					'arguments': value
				});
				return value;
			}
		};
	}
	_html2canvas.Parse = function(images, options) {
		window.scroll(0, 0);

		var element = ((options.elements === undefined) ? document.body : options.elements[0]), // select body by default
			numDraws = 0,
			doc = element.ownerDocument,
			Util = _html2canvas.Util,
			support = Util.Support(options, doc),
			ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"),
			body = doc.body,
			getCSS = Util.getCSS,
			pseudoHide = "___html2canvas___pseudoelement",
			hidePseudoElements = doc.createElement('style');

		hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
			'.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }';

		body.appendChild(hidePseudoElements);

		images = images || {};

		function documentWidth() {
			return Math.max(
				Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
				Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
				Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
			);
		}

		function documentHeight() {
			return Math.max(
				Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
				Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
				Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
			);
		}

		function getCSSInt(element, attribute) {
			var val = parseInt(getCSS(element, attribute), 10);
			return(isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html
		}

		function renderRect(ctx, x, y, w, h, bgcolor) {
			if(bgcolor !== "transparent") {
				ctx.setVariable("fillStyle", bgcolor);
				ctx.fillRect(x, y, w, h);
				numDraws += 1;
			}
		}

		function capitalize(m, p1, p2) {
			if(m.length > 0) {
				return p1 + p2.toUpperCase();
			}
		}

		function textTransform(text, transform) {
			switch(transform) {
				case "lowercase":
					return text.toLowerCase();
				case "capitalize":
					return text.replace(/(^|\s|:|-|\(|\))([a-z])/g, capitalize);
				case "uppercase":
					return text.toUpperCase();
				default:
					return text;
			}
		}

		function noLetterSpacing(letter_spacing) {
			return(/^(normal|none|0px)$/.test(letter_spacing));
		}

		function drawText(currentText, x, y, ctx) {
			if(currentText !== null && Util.trimText(currentText).length > 0) {
				ctx.fillText(currentText, x, y);
				numDraws += 1;
			}
		}

		function setTextVariables(ctx, el, text_decoration, color) {
			var align = false,
				bold = getCSS(el, "fontWeight"),
				family = getCSS(el, "fontFamily"),
				size = getCSS(el, "fontSize"),
				shadows = Util.parseTextShadows(getCSS(el, "textShadow"));

			switch(parseInt(bold, 10)) {
				case 401:
					bold = "bold";
					break;
				case 400:
					bold = "normal";
					break;
			}

			ctx.setVariable("fillStyle", color);
			ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" "));
			ctx.setVariable("textAlign", (align) ? "right" : "left");

			if(shadows.length) {
				// TODO: support multiple text shadows
				// apply the first text shadow
				ctx.setVariable("shadowColor", shadows[0].color);
				ctx.setVariable("shadowOffsetX", shadows[0].offsetX);
				ctx.setVariable("shadowOffsetY", shadows[0].offsetY);
				ctx.setVariable("shadowBlur", shadows[0].blur);
			}

			if(text_decoration !== "none") {
				return Util.Font(family, size, doc);
			}
		}

		function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) {
			switch(text_decoration) {
				case "underline":
					// Draws a line at the baseline of the font
					// TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size
					renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color);
					break;
				case "overline":
					renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color);
					break;
				case "line-through":
					// TODO try and find exact position for line-through
					renderRect(ctx, bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, color);
					break;
			}
		}

		function getTextBounds(state, text, textDecoration, isLast, transform) {
			var bounds;
			if(support.rangeBounds && !transform) {
				if(textDecoration !== "none" || Util.trimText(text).length !== 0) {
					bounds = textRangeBounds(text, state.node, state.textOffset);
				}
				state.textOffset += text.length;
			} else if(state.node && typeof state.node.nodeValue === "string") {
				var newTextNode = (isLast) ? state.node.splitText(text.length) : null;
				bounds = textWrapperBounds(state.node, transform);
				state.node = newTextNode;
			}
			return bounds;
		}

		function textRangeBounds(text, textNode, textOffset) {
			var range = doc.createRange();
			range.setStart(textNode, textOffset);
			range.setEnd(textNode, textOffset + text.length);
			return range.getBoundingClientRect();
		}

		function textWrapperBounds(oldTextNode, transform) {
			var parent = oldTextNode.parentNode,
				wrapElement = doc.createElement('wrapper'),
				backupText = oldTextNode.cloneNode(true);

			wrapElement.appendChild(oldTextNode.cloneNode(true));
			parent.replaceChild(wrapElement, oldTextNode);

			var bounds = transform ? Util.OffsetBounds(wrapElement) : Util.Bounds(wrapElement);
			parent.replaceChild(backupText, wrapElement);
			return bounds;
		}

		function renderText(el, textNode, stack) {
			var ctx = stack.ctx,
				color = getCSS(el, "color"),
				textDecoration = getCSS(el, "textDecoration"),
				textAlign = getCSS(el, "textAlign"),
				metrics,
				textList,
				state = {
					node: textNode,
					textOffset: 0
				};

			if(Util.trimText(textNode.nodeValue).length > 0) {
				textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
				textAlign = textAlign.replace(["-webkit-auto"], ["auto"]);

				textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ?
					textNode.nodeValue.split(/(\b| )/) :
					textNode.nodeValue.split("");

				metrics = setTextVariables(ctx, el, textDecoration, color);

				if(options.chinese) {
					textList.forEach(function(word, index) {
						if(/.*[\u4E00-\u9FA5].*$/.test(word)) {
							word = word.split("");
							word.unshift(index, 1);
							textList.splice.apply(textList, word);
						}
					});
				}

				textList.forEach(function(text, index) {
					var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
					if(bounds) {
						drawText(text, bounds.left, bounds.bottom, ctx);
						renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
					}
				});
			}
		}

		function listPosition(element, val) {
			var boundElement = doc.createElement("boundelement"),
				originalType,
				bounds;

			boundElement.style.display = "inline";

			originalType = element.style.listStyleType;
			element.style.listStyleType = "none";

			boundElement.appendChild(doc.createTextNode(val));

			element.insertBefore(boundElement, element.firstChild);

			bounds = Util.Bounds(boundElement);
			element.removeChild(boundElement);
			element.style.listStyleType = originalType;
			return bounds;
		}

		function elementIndex(el) {
			var i = -1,
				count = 1,
				childs = el.parentNode.childNodes;

			if(el.parentNode) {
				while(childs[++i] !== el) {
					if(childs[i].nodeType === 1) {
						count++;
					}
				}
				return count;
			} else {
				return -1;
			}
		}

		function listItemText(element, type) {
			var currentIndex = elementIndex(element),
				text;
			switch(type) {
				case "decimal":
					text = currentIndex;
					break;
				case "decimal-leading-zero":
					text = (currentIndex.toString().length === 1) ? currentIndex = "0" + currentIndex.toString() : currentIndex.toString();
					break;
				case "upper-roman":
					text = _html2canvas.Generate.ListRoman(currentIndex);
					break;
				case "lower-roman":
					text = _html2canvas.Generate.ListRoman(currentIndex).toLowerCase();
					break;
				case "lower-alpha":
					text = _html2canvas.Generate.ListAlpha(currentIndex).toLowerCase();
					break;
				case "upper-alpha":
					text = _html2canvas.Generate.ListAlpha(currentIndex);
					break;
			}

			return text + ". ";
		}

		function renderListItem(element, stack, elBounds) {
			var x,
				text,
				ctx = stack.ctx,
				type = getCSS(element, "listStyleType"),
				listBounds;

			if(/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) {
				text = listItemText(element, type);
				listBounds = listPosition(element, text);
				setTextVariables(ctx, element, "none", getCSS(element, "color"));

				if(getCSS(element, "listStylePosition") === "inside") {
					ctx.setVariable("textAlign", "left");
					x = elBounds.left;
				} else {
					return;
				}

				drawText(text, x, listBounds.bottom, ctx);
			}
		}

		function loadImage(src) {
			var img = images[src];
			return(img && img.succeeded === true) ? img.img : false;
		}

		function clipBounds(src, dst) {
			var x = Math.max(src.left, dst.left),
				y = Math.max(src.top, dst.top),
				x2 = Math.min((src.left + src.width), (dst.left + dst.width)),
				y2 = Math.min((src.top + src.height), (dst.top + dst.height));

			return {
				left: x,
				top: y,
				width: x2 - x,
				height: y2 - y
			};
		}

		function setZ(element, stack, parentStack) {
			var newContext,
				isPositioned = stack.cssPosition !== 'static',
				zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto',
				opacity = getCSS(element, 'opacity'),
				isFloated = getCSS(element, 'cssFloat') !== 'none';

			// https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
			// When a new stacking context should be created:
			// the root element (HTML),
			// positioned (absolutely or relatively) with a z-index value other than "auto",
			// elements with an opacity value less than 1. (See the specification for opacity),
			// on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post)

			stack.zIndex = newContext = h2czContext(zIndex);
			newContext.isPositioned = isPositioned;
			newContext.isFloated = isFloated;
			newContext.opacity = opacity;
			newContext.ownStacking = (zIndex !== 'auto' || opacity < 1);

			if(parentStack) {
				parentStack.zIndex.children.push(stack);
			}
		}

		function renderImage(ctx, element, image, bounds, borders) {

			var paddingLeft = getCSSInt(element, 'paddingLeft'),
				paddingTop = getCSSInt(element, 'paddingTop'),
				paddingRight = getCSSInt(element, 'paddingRight'),
				paddingBottom = getCSSInt(element, 'paddingBottom');

			drawImage(
				ctx,
				image,
				0, //sx
				0, //sy
				image.width, //sw
				image.height, //sh
				bounds.left + paddingLeft + borders[3].width, //dx
				bounds.top + paddingTop + borders[0].width, // dy
				bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), //dw
				bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh
			);
		}

		function getBorderData(element) {
			return ["Top", "Right", "Bottom", "Left"].map(function(side) {
				return {
					width: getCSSInt(element, 'border' + side + 'Width'),
					color: getCSS(element, 'border' + side + 'Color')
				};
			});
		}

		function getBorderRadiusData(element) {
			return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
				return getCSS(element, 'border' + side + 'Radius');
			});
		}

		var getCurvePoints = (function(kappa) {

			return function(x, y, r1, r2) {
				var ox = (r1) * kappa, // control point offset horizontal
					oy = (r2) * kappa, // control point offset vertical
					xm = x + r1, // x-middle
					ym = y + r2; // y-middle
				return {
					topLeft: bezierCurve({
						x: x,
						y: ym
					}, {
						x: x,
						y: ym - oy
					}, {
						x: xm - ox,
						y: y
					}, {
						x: xm,
						y: y
					}),
					topRight: bezierCurve({
						x: x,
						y: y
					}, {
						x: x + ox,
						y: y
					}, {
						x: xm,
						y: ym - oy
					}, {
						x: xm,
						y: ym
					}),
					bottomRight: bezierCurve({
						x: xm,
						y: y
					}, {
						x: xm,
						y: y + oy
					}, {
						x: x + ox,
						y: ym
					}, {
						x: x,
						y: ym
					}),
					bottomLeft: bezierCurve({
						x: xm,
						y: ym
					}, {
						x: xm - ox,
						y: ym
					}, {
						x: x,
						y: y + oy
					}, {
						x: x,
						y: y
					})
				};
			};
		})(4 * ((Math.sqrt(2) - 1) / 3));

		function bezierCurve(start, startControl, endControl, end) {

			var lerp = function(a, b, t) {
				return {
					x: a.x + (b.x - a.x) * t,
					y: a.y + (b.y - a.y) * t
				};
			};

			return {
				start: start,
				startControl: startControl,
				endControl: endControl,
				end: end,
				subdivide: function(t) {
					var ab = lerp(start, startControl, t),
						bc = lerp(startControl, endControl, t),
						cd = lerp(endControl, end, t),
						abbc = lerp(ab, bc, t),
						bccd = lerp(bc, cd, t),
						dest = lerp(abbc, bccd, t);
					return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
				},
				curveTo: function(borderArgs) {
					borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
				},
				curveToReversed: function(borderArgs) {
					borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
				}
			};
		}

		function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
			if(radius1[0] > 0 || radius1[1] > 0) {
				borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
				corner1[0].curveTo(borderArgs);
				corner1[1].curveTo(borderArgs);
			} else {
				borderArgs.push(["line", x, y]);
			}

			if(radius2[0] > 0 || radius2[1] > 0) {
				borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
			}
		}

		function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
			var borderArgs = [];

			if(radius1[0] > 0 || radius1[1] > 0) {
				borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
				outer1[1].curveTo(borderArgs);
			} else {
				borderArgs.push(["line", borderData.c1[0], borderData.c1[1]]);
			}

			if(radius2[0] > 0 || radius2[1] > 0) {
				borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
				outer2[0].curveTo(borderArgs);
				borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
				inner2[0].curveToReversed(borderArgs);
			} else {
				borderArgs.push(["line", borderData.c2[0], borderData.c2[1]]);
				borderArgs.push(["line", borderData.c3[0], borderData.c3[1]]);
			}

			if(radius1[0] > 0 || radius1[1] > 0) {
				borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
				inner1[1].curveToReversed(borderArgs);
			} else {
				borderArgs.push(["line", borderData.c4[0], borderData.c4[1]]);
			}

			return borderArgs;
		}

		function calculateCurvePoints(bounds, borderRadius, borders) {

			var x = bounds.left,
				y = bounds.top,
				width = bounds.width,
				height = bounds.height,

				tlh = borderRadius[0][0],
				tlv = borderRadius[0][1],
				trh = borderRadius[1][0],
				trv = borderRadius[1][1],
				brh = borderRadius[2][0],
				brv = borderRadius[2][1],
				blh = borderRadius[3][0],
				blv = borderRadius[3][1],

				topWidth = width - trh,
				rightHeight = height - brv,
				bottomWidth = width - brh,
				leftHeight = height - blv;

			return {
				topLeftOuter: getCurvePoints(
					x,
					y,
					tlh,
					tlv
				).topLeft.subdivide(0.5),

				topLeftInner: getCurvePoints(
					x + borders[3].width,
					y + borders[0].width,
					Math.max(0, tlh - borders[3].width),
					Math.max(0, tlv - borders[0].width)
				).topLeft.subdivide(0.5),

				topRightOuter: getCurvePoints(
					x + topWidth,
					y,
					trh,
					trv
				).topRight.subdivide(0.5),

				topRightInner: getCurvePoints(
					x + Math.min(topWidth, width + borders[3].width),
					y + borders[0].width,
					(topWidth > width + borders[3].width) ? 0 : trh - borders[3].width,
					trv - borders[0].width
				).topRight.subdivide(0.5),

				bottomRightOuter: getCurvePoints(
					x + bottomWidth,
					y + rightHeight,
					brh,
					brv
				).bottomRight.subdivide(0.5),

				bottomRightInner: getCurvePoints(
					x + Math.min(bottomWidth, width + borders[3].width),
					y + Math.min(rightHeight, height + borders[0].width),
					Math.max(0, brh - borders[1].width),
					Math.max(0, brv - borders[2].width)
				).bottomRight.subdivide(0.5),

				bottomLeftOuter: getCurvePoints(
					x,
					y + leftHeight,
					blh,
					blv
				).bottomLeft.subdivide(0.5),

				bottomLeftInner: getCurvePoints(
					x + borders[3].width,
					y + leftHeight,
					Math.max(0, blh - borders[3].width),
					Math.max(0, blv - borders[2].width)
				).bottomLeft.subdivide(0.5)
			};
		}

		function getBorderClip(element, borderPoints, borders, radius, bounds) {
			var backgroundClip = getCSS(element, 'backgroundClip'),
				borderArgs = [];

			switch(backgroundClip) {
				case "content-box":
				case "padding-box":
					parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
					parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
					parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
					parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
					break;

				default:
					parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
					parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
					parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
					parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
					break;
			}

			return borderArgs;
		}

		function parseBorders(element, bounds, borders) {
			var x = bounds.left,
				y = bounds.top,
				width = bounds.width,
				height = bounds.height,
				borderSide,
				bx,
				by,
				bw,
				bh,
				borderArgs,
				// http://www.w3.org/TR/css3-background/#the-border-radius
				borderRadius = getBorderRadiusData(element),
				borderPoints = calculateCurvePoints(bounds, borderRadius, borders),
				borderData = {
					clip: getBorderClip(element, borderPoints, borders, borderRadius, bounds),
					borders: []
				};

			for(borderSide = 0; borderSide < 4; borderSide++) {

				if(borders[borderSide].width > 0) {
					bx = x;
					by = y;
					bw = width;
					bh = height - (borders[2].width);

					switch(borderSide) {
						case 0:
							// top border
							bh = borders[0].width;

							borderArgs = drawSide({
									c1: [bx, by],
									c2: [bx + bw, by],
									c3: [bx + bw - borders[1].width, by + bh],
									c4: [bx + borders[3].width, by + bh]
								}, borderRadius[0], borderRadius[1],
								borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
							break;
						case 1:
							// right border
							bx = x + width - (borders[1].width);
							bw = borders[1].width;

							borderArgs = drawSide({
									c1: [bx + bw, by],
									c2: [bx + bw, by + bh + borders[2].width],
									c3: [bx, by + bh],
									c4: [bx, by + borders[0].width]
								}, borderRadius[1], borderRadius[2],
								borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
							break;
						case 2:
							// bottom border
							by = (by + height) - (borders[2].width);
							bh = borders[2].width;

							borderArgs = drawSide({
									c1: [bx + bw, by + bh],
									c2: [bx, by + bh],
									c3: [bx + borders[3].width, by],
									c4: [bx + bw - borders[3].width, by]
								}, borderRadius[2], borderRadius[3],
								borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
							break;
						case 3:
							// left border
							bw = borders[3].width;

							borderArgs = drawSide({
									c1: [bx, by + bh + borders[2].width],
									c2: [bx, by],
									c3: [bx + bw, by + borders[0].width],
									c4: [bx + bw, by + bh]
								}, borderRadius[3], borderRadius[0],
								borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
							break;
					}

					borderData.borders.push({
						args: borderArgs,
						color: borders[borderSide].color
					});

				}
			}

			return borderData;
		}

		function createShape(ctx, args) {
			var shape = ctx.drawShape();
			args.forEach(function(border, index) {
				shape[(index === 0) ? "moveTo" : border[0] + "To"].apply(null, border.slice(1));
			});
			return shape;
		}

		function renderBorders(ctx, borderArgs, color) {
			if(color !== "transparent") {
				ctx.setVariable("fillStyle", color);
				createShape(ctx, borderArgs);
				ctx.fill();
				numDraws += 1;
			}
		}

		function renderFormValue(el, bounds, stack) {

			var valueWrap = doc.createElement('valuewrap'),
				cssPropertyArray = ['lineHeight', 'textAlign', 'fontFamily', 'color', 'fontSize', 'paddingLeft', 'paddingTop', 'width', 'height', 'border', 'borderLeftWidth', 'borderTopWidth'],
				textValue,
				textNode;

			cssPropertyArray.forEach(function(property) {
				try {
					valueWrap.style[property] = getCSS(el, property);
				} catch(e) {
					// Older IE has issues with "border"
					Util.log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
				}
			});

			valueWrap.style.borderColor = "black";
			valueWrap.style.borderStyle = "solid";
			valueWrap.style.display = "block";
			valueWrap.style.position = "absolute";

			if(/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT") {
				valueWrap.style.lineHeight = getCSS(el, "height");
			}

			valueWrap.style.top = bounds.top + "px";
			valueWrap.style.left = bounds.left + "px";

			textValue = (el.nodeName === "SELECT") ? (el.options[el.selectedIndex] || 0).text : el.value;
			if(!textValue) {
				textValue = el.placeholder;
			}

			textNode = doc.createTextNode(textValue);

			valueWrap.appendChild(textNode);
			body.appendChild(valueWrap);

			renderText(el, textNode, stack);
			body.removeChild(valueWrap);
		}

		function drawImage(ctx) {
			ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1));
			numDraws += 1;
		}

		function getPseudoElement(el, which) {
			var elStyle = window.getComputedStyle(el, which);
			if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") {
				return;
			}
			var content = elStyle.content + '',
				first = content.substr(0, 1);
			//strips quotes
			if(first === content.substr(content.length - 1) && first.match(/'|"/)) {
				content = content.substr(1, content.length - 2);
			}

			var isImage = content.substr(0, 3) === 'url',
				elps = document.createElement(isImage ? 'img' : 'span');

			elps.className = pseudoHide + "-before " + pseudoHide + "-after";

			Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
				// Prevent assigning of read only CSS Rules, ex. length, parentRule
				try {
					elps.style[prop] = elStyle[prop];
				} catch(e) {
					Util.log(['Tried to assign readonly property ', prop, 'Error:', e]);
				}
			});

			if(isImage) {
				elps.src = Util.parseBackgroundImage(content)[0].args[0];
			} else {
				elps.innerHTML = content;
			}
			return elps;
		}

		function indexedProperty(property) {
			return(isNaN(window.parseInt(property, 10)));
		}

		function injectPseudoElements(el, stack) {
			var before = getPseudoElement(el, ':before'),
				after = getPseudoElement(el, ':after');
			if(!before && !after) {
				return;
			}

			if(before) {
				el.className += " " + pseudoHide + "-before";
				el.parentNode.insertBefore(before, el);
				parseElement(before, stack, true);
				el.parentNode.removeChild(before);
				el.className = el.className.replace(pseudoHide + "-before", "").trim();
			}

			if(after) {
				el.className += " " + pseudoHide + "-after";
				el.appendChild(after);
				parseElement(after, stack, true);
				el.removeChild(after);
				el.className = el.className.replace(pseudoHide + "-after", "").trim();
			}

		}

		function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
			var offsetX = Math.round(bounds.left + backgroundPosition.left),
				offsetY = Math.round(bounds.top + backgroundPosition.top);

			ctx.createPattern(image);
			ctx.translate(offsetX, offsetY);
			ctx.fill();
			ctx.translate(-offsetX, -offsetY);
		}

		function backgroundRepeatShape(ctx, image, backgroundPosition, bounds, left, top, width, height) {
			var args = [];
			args.push(["line", Math.round(left), Math.round(top)]);
			args.push(["line", Math.round(left + width), Math.round(top)]);
			args.push(["line", Math.round(left + width), Math.round(height + top)]);
			args.push(["line", Math.round(left), Math.round(height + top)]);
			createShape(ctx, args);
			ctx.save();
			ctx.clip();
			renderBackgroundRepeat(ctx, image, backgroundPosition, bounds);
			ctx.restore();
		}

		function renderBackgroundColor(ctx, backgroundBounds, bgcolor) {
			renderRect(
				ctx,
				backgroundBounds.left,
				backgroundBounds.top,
				backgroundBounds.width,
				backgroundBounds.height,
				bgcolor
			);
		}

		function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) {
			var backgroundSize = Util.BackgroundSize(el, bounds, image, imageIndex),
				backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
				backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(Util.trimText);

			image = resizeImage(image, backgroundSize);

			backgroundRepeat = backgroundRepeat[imageIndex] || backgroundRepeat[0];

			switch(backgroundRepeat) {
				case "repeat-x":
					backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
						bounds.left, bounds.top + backgroundPosition.top, 99999, image.height);
					break;

				case "repeat-y":
					backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
						bounds.left + backgroundPosition.left, bounds.top, image.width, 99999);
					break;

				case "no-repeat":
					backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
						bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height);
					break;

				default:
					renderBackgroundRepeat(ctx, image, backgroundPosition, {
						top: bounds.top,
						left: bounds.left,
						width: image.width,
						height: image.height
					});
					break;
			}
		}

		function renderBackgroundImage(element, bounds, ctx) {
			var backgroundImage = getCSS(element, "backgroundImage"),
				backgroundImages = Util.parseBackgroundImage(backgroundImage),
				image,
				imageIndex = backgroundImages.length;

			while(imageIndex--) {
				backgroundImage = backgroundImages[imageIndex];

				if(!backgroundImage.args || backgroundImage.args.length === 0) {
					continue;
				}

				var key = backgroundImage.method === 'url' ?
					backgroundImage.args[0] :
					backgroundImage.value;

				image = loadImage(key);

				// TODO add support for background-origin
				if(image) {
					renderBackgroundRepeating(element, bounds, ctx, image, imageIndex);
				} else {
					Util.log("html2canvas: Error loading background:", backgroundImage);
				}
			}
		}

		function resizeImage(image, bounds) {
			if(image.width === bounds.width && image.height === bounds.height) {
				return image;
			}

			var ctx, canvas = doc.createElement('canvas');
			canvas.width = bounds.width;
			canvas.height = bounds.height;
			ctx = canvas.getContext("2d");
			drawImage(ctx, image, 0, 0, image.width, image.height, 0, 0, bounds.width, bounds.height);
			return canvas;
		}

		function setOpacity(ctx, element, parentStack) {
			return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1));
		}

		function removePx(str) {
			return str.replace("px", "");
		}

		var transformRegExp = /(matrix)\((.+)\)/;

		function getTransform(element, parentStack) {
			var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
			var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";

			transformOrigin = transformOrigin.split(" ").map(removePx).map(Util.asFloat);

			var matrix;
			if(transform && transform !== "none") {
				var match = transform.match(transformRegExp);
				if(match) {
					switch(match[1]) {
						case "matrix":
							matrix = match[2].split(",").map(Util.trimText).map(Util.asFloat);
							break;
					}
				}
			}

			return {
				origin: transformOrigin,
				matrix: matrix
			};
		}

		function createStack(element, parentStack, bounds, transform) {
			var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width, (!parentStack) ? documentHeight() : bounds.height),
				stack = {
					ctx: ctx,
					opacity: setOpacity(ctx, element, parentStack),
					cssPosition: getCSS(element, "position"),
					borders: getBorderData(element),
					transform: transform,
					clip: (parentStack && parentStack.clip) ? Util.Extend({}, parentStack.clip) : null
				};

			setZ(element, stack, parentStack);

			// TODO correct overflow for absolute content residing under a static position
			if(options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false) {
				stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds;
			}

			return stack;
		}

		function getBackgroundBounds(borders, bounds, clip) {
			var backgroundBounds = {
				left: bounds.left + borders[3].width,
				top: bounds.top + borders[0].width,
				width: bounds.width - (borders[1].width + borders[3].width),
				height: bounds.height - (borders[0].width + borders[2].width)
			};

			if(clip) {
				backgroundBounds = clipBounds(backgroundBounds, clip);
			}

			return backgroundBounds;
		}

		function getBounds(element, transform) {
			var bounds = (transform.matrix) ? Util.OffsetBounds(element) : Util.Bounds(element);
			transform.origin[0] += bounds.left;
			transform.origin[1] += bounds.top;
			return bounds;
		}

		function renderElement(element, parentStack, pseudoElement, ignoreBackground) {
			var transform = getTransform(element, parentStack),
				bounds = getBounds(element, transform),
				image,
				stack = createStack(element, parentStack, bounds, transform),
				borders = stack.borders,
				ctx = stack.ctx,
				backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
				borderData = parseBorders(element, bounds, borders),
				backgroundColor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor");

			createShape(ctx, borderData.clip);

			ctx.save();
			ctx.clip();

			if(backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) {
				renderBackgroundColor(ctx, bounds, backgroundColor);
				renderBackgroundImage(element, backgroundBounds, ctx);
			} else if(ignoreBackground) {
				stack.backgroundColor = backgroundColor;
			}

			ctx.restore();

			borderData.borders.forEach(function(border) {
				renderBorders(ctx, border.args, border.color);
			});

			if(!pseudoElement) {
				injectPseudoElements(element, stack);
			}

			switch(element.nodeName) {
				case "IMG":
					if((image = loadImage(element.getAttribute('src')))) {
						renderImage(ctx, element, image, bounds, borders);
					} else {
						Util.log("html2canvas: Error loading <img>:" + element.getAttribute('src'));
					}
					break;
				case "INPUT":
					// TODO add all relevant type's, i.e. HTML5 new stuff
					// todo add support for placeholder attribute for browsers which support it
					if(/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder || "").length > 0) {
						renderFormValue(element, bounds, stack);
					}
					break;
				case "TEXTAREA":
					if((element.value || element.placeholder || "").length > 0) {
						renderFormValue(element, bounds, stack);
					}
					break;
				case "SELECT":
					if((element.options || element.placeholder || "").length > 0) {
						renderFormValue(element, bounds, stack);
					}
					break;
				case "LI":
					renderListItem(element, stack, backgroundBounds);
					break;
				case "CANVAS":
					renderImage(ctx, element, element, bounds, borders);
					break;
			}

			return stack;
		}

		function isElementVisible(element) {
			return(getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
		}

		function parseElement(element, stack, pseudoElement) {
			if(isElementVisible(element)) {
				stack = renderElement(element, stack, pseudoElement, false) || stack;
				if(!ignoreElementsRegExp.test(element.nodeName)) {
					parseChildren(element, stack, pseudoElement);
				}
			}
		}

		function parseChildren(element, stack, pseudoElement) {
			Util.Children(element).forEach(function(node) {
				if(node.nodeType === node.ELEMENT_NODE) {
					parseElement(node, stack, pseudoElement);
				} else if(node.nodeType === node.TEXT_NODE) {
					renderText(element, node, stack);
				}
			});
		}

		function init() {
			var background = getCSS(document.documentElement, "backgroundColor"),
				transparentBackground = (Util.isTransparent(background) && element === document.body),
				stack = renderElement(element, null, false, transparentBackground);
			parseChildren(element, stack);

			if(transparentBackground) {
				background = stack.backgroundColor;
			}

			body.removeChild(hidePseudoElements);
			return {
				backgroundColor: background,
				stack: stack
			};
		}

		return init();
	};

	function h2czContext(zindex) {
		return {
			zindex: zindex,
			children: []
		};
	}

	_html2canvas.Preload = function(options) {

		var images = {
				numLoaded: 0, // also failed are counted here
				numFailed: 0,
				numTotal: 0,
				cleanupDone: false
			},
			pageOrigin,
			Util = _html2canvas.Util,
			methods,
			i,
			count = 0,
			element = options.elements[0] || document.body,
			doc = element.ownerDocument,
			domImages = element.getElementsByTagName('img'), // Fetch images of the present element only
			imgLen = domImages.length,
			link = doc.createElement("a"),
			supportCORS = (function(img) {
				return(img.crossOrigin !== undefined);
			})(new Image()),
			timeoutTimer;

		link.href = window.location.href;
		pageOrigin = link.protocol + link.host;

		function isSameOrigin(url) {
			link.href = url;
			link.href = link.href; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/
			var origin = link.protocol + link.host;
			return(origin === pageOrigin);
		}

		function start() {
			Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")");
			if(!images.firstRun && images.numLoaded >= images.numTotal) {
				Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");

				if(typeof options.complete === "function") {
					options.complete(images);
				}

			}
		}

		// TODO modify proxy to serve images with CORS enabled, where available
		function proxyGetImage(url, img, imageObj) {
			var callback_name,
				scriptUrl = options.proxy,
				script;

			link.href = url;
			url = link.href; // work around for pages with base href="" set - WARNING: this may change the url

			callback_name = 'html2canvas_' + (count++);
			imageObj.callbackname = callback_name;

			if(scriptUrl.indexOf("?") > -1) {
				scriptUrl += "&";
			} else {
				scriptUrl += "?";
			}
			scriptUrl += 'url=' + encodeURIComponent(url) + '&callback=' + callback_name;
			script = doc.createElement("script");

			window[callback_name] = function(a) {
				if(a.substring(0, 6) === "error:") {
					imageObj.succeeded = false;
					images.numLoaded++;
					images.numFailed++;
					start();
				} else {
					setImageLoadHandlers(img, imageObj);
					img.src = a;
				}
				window[callback_name] = undefined; // to work with IE<9  // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
				try {
					delete window[callback_name]; // for all browser that support this
				} catch(ex) {}
				script.parentNode.removeChild(script);
				script = null;
				delete imageObj.script;
				delete imageObj.callbackname;
			};

			script.setAttribute("type", "text/javascript");
			script.setAttribute("src", scriptUrl);
			imageObj.script = script;
			window.document.body.appendChild(script);

		}

		function loadPseudoElement(element, type) {
			var style = window.getComputedStyle(element, type),
				content = style.content;
			if(content.substr(0, 3) === 'url') {
				methods.loadImage(_html2canvas.Util.parseBackgroundImage(content)[0].args[0]);
			}
			loadBackgroundImages(style.backgroundImage, element);
		}

		function loadPseudoElementImages(element) {
			loadPseudoElement(element, ":before");
			loadPseudoElement(element, ":after");
		}

		function loadGradientImage(backgroundImage, bounds) {
			var img = _html2canvas.Generate.Gradient(backgroundImage, bounds);

			if(img !== undefined) {
				images[backgroundImage] = {
					img: img,
					succeeded: true
				};
				images.numTotal++;
				images.numLoaded++;
				start();
			}
		}

		function invalidBackgrounds(background_image) {
			return(background_image && background_image.method && background_image.args && background_image.args.length > 0);
		}

		function loadBackgroundImages(background_image, el) {
			var bounds;

			_html2canvas.Util.parseBackgroundImage(background_image).filter(invalidBackgrounds).forEach(function(background_image) {
				if(background_image.method === 'url') {
					methods.loadImage(background_image.args[0]);
				} else if(background_image.method.match(/\-?gradient$/)) {
					if(bounds === undefined) {
						bounds = _html2canvas.Util.Bounds(el);
					}
					loadGradientImage(background_image.value, bounds);
				}
			});
		}

		function getImages(el) {
			var elNodeType = false;

			// Firefox fails with permission denied on pages with iframes
			try {
				Util.Children(el).forEach(getImages);
			} catch(e) {}

			try {
				elNodeType = el.nodeType;
			} catch(ex) {
				elNodeType = false;
				Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message);
			}

			if(elNodeType === 1 || elNodeType === undefined) {
				loadPseudoElementImages(el);
				try {
					loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el);
				} catch(e) {
					Util.log("html2canvas: failed to get background-image - Exception: " + e.message);
				}
				loadBackgroundImages(el);
			}
		}

		function setImageLoadHandlers(img, imageObj) {
			img.onload = function() {
				if(imageObj.timer !== undefined) {
					// CORS succeeded
					window.clearTimeout(imageObj.timer);
				}

				images.numLoaded++;
				imageObj.succeeded = true;
				img.onerror = img.onload = null;
				start();
			};
			img.onerror = function() {
				if(img.crossOrigin === "anonymous") {
					// CORS failed
					window.clearTimeout(imageObj.timer);

					// let's try with proxy instead
					if(options.proxy) {
						var src = img.src;
						img = new Image();
						imageObj.img = img;
						img.src = src;

						proxyGetImage(img.src, img, imageObj);
						return;
					}
				}

				images.numLoaded++;
				images.numFailed++;
				imageObj.succeeded = false;
				img.onerror = img.onload = null;
				start();
			};
		}

		methods = {
			loadImage: function(src) {
				var img, imageObj;
				if(src && images[src] === undefined) {
					img = new Image();
					if(src.match(/data:image\/.*;base64,/i)) {
						img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, '');
						imageObj = images[src] = {
							img: img
						};
						images.numTotal++;
						setImageLoadHandlers(img, imageObj);
					} else if(isSameOrigin(src) || options.allowTaint === true) {
						imageObj = images[src] = {
							img: img
						};
						images.numTotal++;
						setImageLoadHandlers(img, imageObj);
						img.src = src;
					} else if(supportCORS && !options.allowTaint && options.useCORS) {
						// attempt to load with CORS

						img.crossOrigin = "anonymous";
						imageObj = images[src] = {
							img: img
						};
						images.numTotal++;
						setImageLoadHandlers(img, imageObj);
						img.src = src;
					} else if(options.proxy) {
						imageObj = images[src] = {
							img: img
						};
						images.numTotal++;
						proxyGetImage(src, img, imageObj);
					}
				}

			},
			cleanupDOM: function(cause) {
				var img, src;
				if(!images.cleanupDone) {
					if(cause && typeof cause === "string") {
						Util.log("html2canvas: Cleanup because: " + cause);
					} else {
						Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
					}

					for(src in images) {
						if(images.hasOwnProperty(src)) {
							img = images[src];
							if(typeof img === "object" && img.callbackname && img.succeeded === undefined) {
								// cancel proxy image request
								window[img.callbackname] = undefined; // to work with IE<9  // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
								try {
									delete window[img.callbackname]; // for all browser that support this
								} catch(ex) {}
								if(img.script && img.script.parentNode) {
									img.script.setAttribute("src", "about:blank"); // try to cancel running request
									img.script.parentNode.removeChild(img.script);
								}
								images.numLoaded++;
								images.numFailed++;
								Util.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal);
							}
						}
					}

					// cancel any pending requests
					if(window.stop !== undefined) {
						window.stop();
					} else if(document.execCommand !== undefined) {
						document.execCommand("Stop", false);
					}
					if(document.close !== undefined) {
						document.close();
					}
					images.cleanupDone = true;
					if(!(cause && typeof cause === "string")) {
						start();
					}
				}
			},

			renderingDone: function() {
				if(timeoutTimer) {
					window.clearTimeout(timeoutTimer);
				}
			}
		};

		if(options.timeout > 0) {
			timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout);
		}

		Util.log('html2canvas: Preload starts: finding background-images');
		images.firstRun = true;

		getImages(element);

		Util.log('html2canvas: Preload: Finding images');
		// load <img> images
		for(i = 0; i < imgLen; i += 1) {
			methods.loadImage(domImages[i].getAttribute("src"));
		}

		images.firstRun = false;
		Util.log('html2canvas: Preload: Done.');
		if(images.numTotal === images.numLoaded) {
			start();
		}

		return methods;
	};

	_html2canvas.Renderer = function(parseQueue, options) {

		// http://www.w3.org/TR/CSS21/zindex.html
		function createRenderQueue(parseQueue) {
			var queue = [],
				rootContext;

			rootContext = (function buildStackingContext(rootNode) {
				var rootContext = {};

				function insert(context, node, specialParent) {
					var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex),
						contextForChildren = context, // the stacking context for children
						isPositioned = node.zIndex.isPositioned,
						isFloated = node.zIndex.isFloated,
						stub = {
							node: node
						},
						childrenDest = specialParent; // where children without z-index should be pushed into

					if(node.zIndex.ownStacking) {
						// '!' comes before numbers in sorted array
						contextForChildren = stub.context = {
							'!': [{
								node: node,
								children: []
							}]
						};
						childrenDest = undefined;
					} else if(isPositioned || isFloated) {
						childrenDest = stub.children = [];
					}

					if(zi === 0 && specialParent) {
						specialParent.push(stub);
					} else {
						if(!context[zi]) {
							context[zi] = [];
						}
						context[zi].push(stub);
					}

					node.zIndex.children.forEach(function(childNode) {
						insert(contextForChildren, childNode, childrenDest);
					});
				}
				insert(rootContext, rootNode);
				return rootContext;
			})(parseQueue);

			function sortZ(context) {
				Object.keys(context).sort().forEach(function(zi) {
					var nonPositioned = [],
						floated = [],
						positioned = [],
						list = [];

					// positioned after static
					context[zi].forEach(function(v) {
						if(v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) {
							// http://www.w3.org/TR/css3-color/#transparency
							// non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with â€˜z-index: 0â€™ and â€˜opacity: 1â€™.
							positioned.push(v);
						} else if(v.node.zIndex.isFloated) {
							floated.push(v);
						} else {
							nonPositioned.push(v);
						}
					});

					(function walk(arr) {
						arr.forEach(function(v) {
							list.push(v);
							if(v.children) {
								walk(v.children);
							}
						});
					})(nonPositioned.concat(floated, positioned));

					list.forEach(function(v) {
						if(v.context) {
							sortZ(v.context);
						} else {
							queue.push(v.node);
						}
					});
				});
			}

			sortZ(rootContext);

			return queue;
		}

		function getRenderer(rendererName) {
			var renderer;

			if(typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) {
				renderer = _html2canvas.Renderer[rendererName](options);
			} else if(typeof rendererName === "function") {
				renderer = rendererName(options);
			} else {
				throw new Error("Unknown renderer");
			}

			if(typeof renderer !== "function") {
				throw new Error("Invalid renderer defined");
			}
			return renderer;
		}

		return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue.stack), _html2canvas);
	};

	_html2canvas.Util.Support = function(options, doc) {

		function supportSVGRendering() {
			var img = new Image(),
				canvas = doc.createElement("canvas"),
				ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d");
			if(ctx === false) {
				return false;
			}
			canvas.width = canvas.height = 10;
			img.src = [
				"data:image/svg+xml,",
				"<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'>",
				"<foreignObject width='10' height='10'>",
				"<div xmlns='http://www.w3.org/1999/xhtml' style='width:10;height:10;'>",
				"sup",
				"</div>",
				"</foreignObject>",
				"</svg>"
			].join("");
			try {
				ctx.drawImage(img, 0, 0);
				canvas.toDataURL();
			} catch(e) {
				return false;
			}
			_html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available');
			return true;
		}

		// Test whether we can use ranges to measure bounding boxes
		// Opera doesn't provide valid bounds.height/bottom even though it supports the method.

		function supportRangeBounds() {
			var r, testElement, rangeBounds, rangeHeight, support = false;

			if(doc.createRange) {
				r = doc.createRange();
				if(r.getBoundingClientRect) {
					testElement = doc.createElement('boundtest');
					testElement.style.height = "123px";
					testElement.style.display = "block";
					doc.body.appendChild(testElement);

					r.selectNode(testElement);
					rangeBounds = r.getBoundingClientRect();
					rangeHeight = rangeBounds.height;

					if(rangeHeight === 123) {
						support = true;
					}
					doc.body.removeChild(testElement);
				}
			}

			return support;
		}

		return {
			rangeBounds: supportRangeBounds(),
			svgRendering: options.svgRendering && supportSVGRendering()
		};
	};
	window.html2canvas = function(elements, opts) {
		elements = (elements.length) ? elements : [elements];
		var queue,
			canvas,
			options = {
				// general
				logging: false,
				elements: elements,
				background: "#fff",

				// preload options
				proxy: null,
				timeout: 0, // no timeout
				useCORS: false, // try to load images as CORS (where available), before falling back to proxy
				allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true

				// parse options
				svgRendering: false, // use svg powered rendering where available (FF11+)
				ignoreElements: "IFRAME|OBJECT|PARAM",
				useOverflow: true,
				letterRendering: false,
				chinese: false,

				// render options

				width: null,
				height: null,
				taintTest: true, // do a taint test with all images before applying to canvas
				renderer: "Canvas"
			};

		options = _html2canvas.Util.Extend(opts, options);

		_html2canvas.logging = options.logging;
		options.complete = function(images) {

			if(typeof options.onpreloaded === "function") {
				if(options.onpreloaded(images) === false) {
					return;
				}
			}
			queue = _html2canvas.Parse(images, options);

			if(typeof options.onparsed === "function") {
				if(options.onparsed(queue) === false) {
					return;
				}
			}

			canvas = _html2canvas.Renderer(queue, options);

			if(typeof options.onrendered === "function") {
				options.onrendered(canvas);
			}

		};

		// for pages without images, we still want this to be async, i.e. return methods before executing
		window.setTimeout(function() {
			_html2canvas.Preload(options);
		}, 0);

		return {
			render: function(queue, opts) {
				return _html2canvas.Renderer(queue, _html2canvas.Util.Extend(opts, options));
			},
			parse: function(images, opts) {
				return _html2canvas.Parse(images, _html2canvas.Util.Extend(opts, options));
			},
			preload: function(opts) {
				return _html2canvas.Preload(_html2canvas.Util.Extend(opts, options));
			},
			log: _html2canvas.Util.log
		};
	};

	window.html2canvas.log = _html2canvas.Util.log; // for renderers
	window.html2canvas.Renderer = {
		Canvas: undefined // We are assuming this will be used
	};
	_html2canvas.Renderer.Canvas = function(options) {
		options = options || {};

		var doc = document,
			safeImages = [],
			testCanvas = document.createElement("canvas"),
			testctx = testCanvas.getContext("2d"),
			Util = _html2canvas.Util,
			canvas = options.canvas || doc.createElement('canvas');

		function createShape(ctx, args) {
			ctx.beginPath();
			args.forEach(function(arg) {
				ctx[arg.name].apply(ctx, arg['arguments']);
			});
			ctx.closePath();
		}

		function safeImage(item) {
			if(safeImages.indexOf(item['arguments'][0].src) === -1) {
				testctx.drawImage(item['arguments'][0], 0, 0);
				try {
					testctx.getImageData(0, 0, 1, 1);
				} catch(e) {
					testCanvas = doc.createElement("canvas");
					testctx = testCanvas.getContext("2d");
					return false;
				}
				safeImages.push(item['arguments'][0].src);
			}
			return true;
		}

		function renderItem(ctx, item) {
			switch(item.type) {
				case "variable":
					ctx[item.name] = item['arguments'];
					break;
				case "function":
					switch(item.name) {
						case "createPattern":
							if(item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
								try {
									ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
								} catch(e) {
									Util.log("html2canvas: Renderer: Error creating pattern", e.message);
								}
							}
							break;
						case "drawShape":
							createShape(ctx, item['arguments']);
							break;
						case "drawImage":
							if(item['arguments'][8] > 0 && item['arguments'][7] > 0) {
								if(!options.taintTest || (options.taintTest && safeImage(item))) {
									ctx.drawImage.apply(ctx, item['arguments']);
								}
							}
							break;
						default:
							ctx[item.name].apply(ctx, item['arguments']);
					}
					break;
			}
		}

		return function(parsedData, options, document, queue, _html2canvas) {
			var ctx = canvas.getContext("2d"),
				newCanvas,
				bounds,
				fstyle,
				zStack = parsedData.stack;

			canvas.width = canvas.style.width = options.width || zStack.ctx.width;
			canvas.height = canvas.style.height = options.height || zStack.ctx.height;

			fstyle = ctx.fillStyle;
			ctx.fillStyle = (Util.isTransparent(zStack.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor;
			ctx.fillRect(0, 0, canvas.width, canvas.height);
			ctx.fillStyle = fstyle;

			queue.forEach(function(storageContext) {
				// set common settings for canvas
				ctx.textBaseline = "bottom";
				ctx.save();

				if(storageContext.transform.matrix) {
					ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]);
					ctx.transform.apply(ctx, storageContext.transform.matrix);
					ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]);
				}

				if(storageContext.clip) {
					ctx.beginPath();
					ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
					ctx.clip();
				}

				if(storageContext.ctx.storage) {
					storageContext.ctx.storage.forEach(function(item) {
						renderItem(ctx, item);
					});
				}

				ctx.restore();
			});

			Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj");

			if(options.elements.length === 1) {
				if(typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
					// crop image to the bounds of selected (single) element
					bounds = _html2canvas.Util.Bounds(options.elements[0]);
					newCanvas = document.createElement('canvas');
					newCanvas.width = Math.ceil(bounds.width);
					newCanvas.height = Math.ceil(bounds.height);
					ctx = newCanvas.getContext("2d");

					ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
					canvas = null;
					return newCanvas;
				}
			}

			return canvas;
		};
	};
})(window, document);